/* 
CDX81 - The Calodox ZX81 Emulator
Copyright (c) 2003 Sebastian Gerlach

Uses code from the z81 emulator:
Original xz80 code Copyright (c) 1994 Ian Collier.
z81 changes        Copyright (c) 1995-2001 Russell Marks.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <memory.h>
#include <stdio.h>
#include "z80.h"

#define TSTATESPERFRAME 64998
#define TSTATESPERLINE	207
#define TSTATESPERTAPE	147

#define parity(a) (partable[a])

unsigned char partable[256]={
	4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4,
		0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0,
		0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0,
		4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4,
		0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0,
		4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4,
		4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4,
		0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0,
		0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0,
		4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4,
		4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4,
		0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0,
		4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4,
		0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0,
		0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0,
		4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4
};


static int vsync_toggle=0,vsync_lasttoggle=0;

int final = 0;

void Wave::load(const char *fname)
{
	length=pos=0;
	if(data)
		free(data);
	data=NULL;
	FILE *fptr=fopen(fname,"rb");
	if(fptr)
	{
		fseek(fptr,40,0);
		fread(&length,4,1,fptr);
		data=(unsigned char *)malloc(length);
		fread(data,length,1,fptr);
		for(int i=0;i<length;i++)
			data[i]=(~data[i])&0x80;
		data[0]=0x80;
	}
}


void ZX81Emulator::reset(Mode m, const unsigned char *rom, const char *fname)
{
	mode = m;
	initmem(rom);

	tstates=0;
	lastDisplayPos=0;

	a=f=b=c=d=e=h=l=a1=f1=b1=c1=d1=e1=h1=l1=i=iff1=iff2=im=r=0;

	ixoriy=new_ixoriy=0;
	ix=iy=sp=pc=0;
	tstates=radjust=0;

	nextlinetime=TSTATESPERLINE;

	nextlinetime=0;
	intsample=0;
	ulacharline=0;
	nmipend=0;
	intpend=0;

	for(int i=0;i<9;i++)
		keyports[i]=0xff;

	nmigen=0,hsyncgen=0,vsync=0;

	if(fname)
	{
		FILE *in=fopen(fname,"rb");
		if(in!=NULL)
		{
			fread(&memory[0x4034],1,1024,in);
			fclose(in);
			pc=0x4034;
		}
	}
}

void ZX81Emulator::initmem(const unsigned char *rom)
{
	int f;
	int ramsize;
	int count;

	memset(memory,0,65536);
	memcpy(memory,rom,(mode==ZX80) ? 4096 : 8192);

	/* ROM setup */
	count=0;
	for(f=0;f<16;f++)
	{
		memattr[f]=memattr[32+f]=0;
		memptr[f]=memptr[32+f]=memory+1024*count;
		count++;
		if(count>=((mode==ZX80) ?4:8)) count=0;
	}

	/* RAM setup, stick to 1k for now */
	ramsize=1;
	count=0;
	for(f=16;f<32;f++)
	{
		memattr[f]=memattr[32+f]=1;
		memptr[f]=memptr[32+f]=memory+1024*(16+count);
		count++;
		if(count>=ramsize) count=0;
	}
}

unsigned int ZX81Emulator::in(int h,int l)
{
	int ts=0;		/* additional cycles*256 */
	int tapezeromask=0x80;
	// Check whether a tape is currently running...
	int tapeoff=(int)((__int64)(tstates-tapeSignal.origin)*22050/3250000);
	if(tapeoff<tapeSignal.length)
		tapezeromask=tapeSignal.data[tapeoff];

	if(!(l&4)) l=0xfb;
	if(!(l&1)) l=0xfe;

	switch(l)
	{
	case 0xfe:
		/* also disables hsync/vsync if nmi off
		* (yes, vsync requires nmi off too, Flight Simulation confirms this)
		*/
		if(!nmigen)
		{
			hsyncgen=0;

			vsync=1;
		}

		switch(h)
		{
		case 0xfe:	return(ts|(keyports[0]^tapezeromask));
		case 0xfd:	return(ts|(keyports[1]^tapezeromask));
		case 0xfb:	return(ts|(keyports[2]^tapezeromask));
		case 0xf7:	return(ts|(keyports[3]^tapezeromask));
		case 0xef:	return(ts|(keyports[4]^tapezeromask));
		case 0xdf:	return(ts|(keyports[5]^tapezeromask));
		case 0xbf:	return(ts|(keyports[6]^tapezeromask));
		case 0x7f:	return(ts|(keyports[7]^tapezeromask));
		default:
			{
				int i,mask,retval=0xff;

				/* some games (e.g. ZX Galaxians) do smart-arse things
				* like zero more than one bit. What we have to do to
				* support this is AND together any for which the corresponding
				* bit is zero.
				*/
				for(i=0,mask=1;i<8;i++,mask<<=1)
					if(!(h&mask))
						retval&=keyports[i];
				return(ts|(retval^tapezeromask));
			}
		}
		break;
	}

	return(ts|255);
}


unsigned int ZX81Emulator::out(int h,int l,int a)
{
	/* either in from fe or out to ff takes one extra cycle;
	* experimentation strongly suggests not only that out to
	* ff takes one extra, but that *all* outs do.
	*/
	int ts=1;	/* additional cycles */


	if(!(l&4)) l=0xfb;
	if(!(l&2)) l=0xfd;
	if(!(l&1)) l=0xfe;

	/*printf("out %2X\n",l);*/

	switch(l)
	{
	case 0xfd:
		nmigen=0;
		break;
	case 0xfe:
		if((mode==ZX81))
			nmigen=1;
		break;
	case 0xff:	/* XXX should *any* out turn off vsync? */
		vsync=0;
		hsyncgen=1;
		break;
	}

	return(ts);
}


void ZX81Emulator::runOneTick()
{
	int lastvsync=0;
	int prevvsync=0, curvsync=0;
	while(1)
	{
		// End of a real vsync
//		if(intsample && prevvsync>TSTATESPERFRAME-20*TSTATESPERLINE && lastvsync>400)
		if(intsample && tstates-prevvsync>50*TSTATESPERLINE && lastvsync>400)
		{
			tapeSignal.origin-=tstates;
			nextlinetime=TSTATESPERLINE;
			vsyncstart=0;
			tstates=0;
			lastDisplayPos=0;
			return;
		}
		// Generate fake sync if we've been here too long
		if(intsample && tstates>=TSTATESPERFRAME)
		{
			if(vsync) vsyncstart=tstates;
			nextlinetime-=tstates;
			vsyncstart-=tstates;
			tapeSignal.origin-=tstates;
			tstates=0;
			lastDisplayPos=0;
			return;
		}

		/* this *has* to be checked before radjust is incr'd */
		if(intsample && !(radjust&64))
			intpend=1;

		ixoriy=new_ixoriy;
		new_ixoriy=0;
		intsample=1;
		op=fetch(pc&0x7fff);

		if((pc&0x8000) && !(op&64))
		{
			int pos=tstates<<1;
			if(pos>=ZX_VID_HEIGHT*ZX_VID_WIDTH)
				pos=ZX_VID_HEIGHT*ZX_VID_WIDTH;

			for(;lastDisplayPos<(unsigned)pos;lastDisplayPos++)
				video[lastDisplayPos]=0;

			unsigned char c=0;
			if(i<0x40)
			{
				c=memptr[i>>2][((i&3)<<8)|((op&63)<<3)|ulacharline];
				if(op&0x80) c=~c;
			}
			else
				c=memptr[i>>2][((i&3)<<8)|radjust];
			for(int p=0;p<8;p++)
			{
				video[pos+p]=(c&128) ? 1 : 0;
				c<<=1;
			}
			lastDisplayPos+=8;

			op=0;	/* the CPU sees a nop */
		}

		pc++;
		radjust++;

		int pvsync=vsync;

		switch(op)
		{
#include "z80ops.c"
		}

		if(vsync)
		{
			int pos=tstates<<1;
			if(pos>=ZX_VID_HEIGHT*ZX_VID_WIDTH)
				pos=ZX_VID_HEIGHT*ZX_VID_WIDTH;
			for(;lastDisplayPos<pos;lastDisplayPos++)
				video[lastDisplayPos]=2;
			ulacharline=0;
		}
		else
		{
			int pos=tstates<<1;
			if(pos>=ZX_VID_HEIGHT*ZX_VID_WIDTH)
				pos=ZX_VID_HEIGHT*ZX_VID_WIDTH;
			for(;lastDisplayPos<pos;lastDisplayPos++)
				video[lastDisplayPos]=0;

			if(vsync!=pvsync)
			{
				lastvsync=tstates-vsyncstart;
				prevvsync=curvsync;
				curvsync=tstates;
			}
			vsyncstart=tstates;
		}

		if(tstates>=nextlinetime)	/* new line */
		{
			nextlinetime+=TSTATESPERLINE;

			if(hsyncgen)
			{
				ulacharline++;
				ulacharline&=7;
			}

			if(nmigen)
				nmipend=1;
		}

		if(intsample && nmipend)
		{
			nmipend=0;

			if(nmigen)
			{
				iff2=iff1;
				iff1=0;
				if(fetch(pc&0x7fff)==0x76)
				{
					pc++;
					tstates-=(tstates-nextlinetime+TSTATESPERLINE);
				}
				else
				{
					/* this seems curiously long, but equally, seems
					* to be just about right. :-)
					*/
					tstates+=27;
				}
				push2(pc);
				pc=0x66;
			}
		}

		if(intsample && intpend)
		{
			intpend=0;

			if(iff1)
			{
				/*      printf("int line %d tst %d\n",liney,tstates);*/
				if(fetch(pc&0x7fff)==0x76)
					pc++;
				iff1=iff2=0;

				//tstates+=5; /* accompanied by an input from the data bus */
				tstates+=4; // Well, using 5 here shifted everything, so 4 it is...

				switch(im)
				{
				case 0: /* IM 0 */
				case 1: /* undocumented */
				case 2: /* IM 1 */
					/* there is little to distinguish between these cases */
					tstates+=9; /* perhaps */
					push2(pc);
					pc=0x38;
					break;
				case 3: /* IM 2 */
					/* (seems unlikely it'd ever be used on the '81, but...) */
					tstates+=13; /* perhaps */
					{
						int addr=fetch2((i<<8)|0xff);
						push2(pc);
						pc=addr;
					}
				}
			}
		}
	}
}
